﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace SimpleORM
{
    public class ExpressionToSql : ExpressionTranslater
    {
        #region Properties

        #endregion

        #region Fields

        #region Fields - Keywords

        protected const string SELECT = "Select";
        protected const string SELECTMANY = "SelectMany";
        protected const string GROUPBY = "GroupBy";
        protected const string COUNT = "Count";
        protected const string DISTINCT = "Distinct";
        protected const string AVG = "Average";
        protected const string SUM = "Sum";
        protected const string MAX = "Max";
        protected const string MIN = "Min";
        protected const string FROM = "From";
        protected const string WHERE = "Where";
        protected const string ORDERBY = "OrderBy";
        protected const string FIRST = "First";
        protected const string FIRSTORDEFAULT = "FirstOrDefault";
        protected const string ANY = "Any";

        protected const string ADD = "Insert";
        protected const string UPDATE = "Update";
        protected const string DELETE = "Delete";

        #endregion

        #region Fields - Mapping

        protected FromNameDictionary FromNameDictionary { get; private set; }
        protected ObjectContextDictionary ObjectContextDictionary { get; private set; }

        #endregion

        #region Fields - Flag

        private Type GroupByType;
        private Dictionary<string, IScriptFactory> GroupByKeys;
        //private IScriptFactory lastGroupByKey;
        //private Expression lastJoinSelect;

        #endregion

        #endregion

        #region Ctor

        public ExpressionToSql()
        {
            ResultsPriority = new object[]
            {
                new object[]{ COUNT, AVG, SUM, MAX, MIN, UPDATE, DELETE, SELECT },
                new object[]{ ADD, FROM },
                new object[]{ WHERE },
                new object[]{ GROUPBY },
                new object[]{ ORDERBY }
            };

            ResultType = eExpressionResultType.Enumerable;
            FromNameDictionary = new FromNameDictionary();
            ObjectContextDictionary = new ObjectContextDictionary();
            GroupByKeys = new Dictionary<string, IScriptFactory>();
        }

        #endregion

        #region Methods

        #region Methods - Handler

        #region Methods - Handler - Edit

        [ExpressionTranslater(MethodName = "Add")]
        public virtual void AddHandler(MethodCallExpression exp)
        {
            AddTranslator t = new AddTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp);

            GetStack(ADD).Push(t.Result);

            ResultType = eExpressionResultType.Number;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        [ExpressionTranslater(MethodName = UPDATE)]
        public virtual void UpdateHandler(MethodCallExpression exp)
        {
            LambdaExpression lambda = exp.FindLambda();

            if (lambda != null)
            {
                UpdateLambdaTranslator t = new UpdateLambdaTranslator();
                t.DataSource = DataSource;
                t.ObjectContextDictionary = ObjectContextDictionary;
                t.FromNameDictionary = FromNameDictionary;
                t.Translate(exp);

                CurrentStack.Push(t.Result);
            }
            else
            {
                UpdateTranslator t = new UpdateTranslator();
                t.DataSource = DataSource;
                t.ObjectContextDictionary = ObjectContextDictionary;
                t.FromNameDictionary = FromNameDictionary;
                t.Translate(exp);

                CurrentStack.Push(t.Result);
            }

            ResultType = eExpressionResultType.Number;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        [ExpressionTranslater(MethodName = DELETE)]
        public virtual void DeleteHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            DeleteTranslator t = new DeleteTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp);

            CurrentStack.Push(t.Result);

            ResultType = eExpressionResultType.Number;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        #endregion

        #region Methods - Handler - Query

        [ExpressionTranslater(MethodName = SELECT)]
        public virtual void SelectHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            SelectTranslator t = new SelectTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.GroupByKeys = GroupByKeys;
            t.GroupByType = GroupByType;
            t.Translate(exp.FindLambda());

            ResultObjectContext = ObjectContextDictionary.Get(t.ObjectType);

            CurrentStack.Push(t.Result);

            //lastJoinSelect = null;
        }

        [ExpressionTranslater(MethodName = WHERE)]
        public virtual void WhereHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            MethodCallExpression m = exp.Arguments[0] as MethodCallExpression;
            if (m != null && m.Method.Name == SELECTMANY)
            {
                LeftJoinTranslator t = new LeftJoinTranslator();
                t.DataSource = DataSource;
                t.ObjectContextDictionary = ObjectContextDictionary;
                t.FromNameDictionary = FromNameDictionary;
                t.Translate(m, exp.FindLambda());

                GetStack(FROM).Push(GetStack(FROM).Pop().
                               Concat(t.Result, " ").Bracket());
            }
            else
            {
                LambdaTranslator t = new LambdaTranslator();
                t.DataSource = DataSource;
                t.ObjectContextDictionary = ObjectContextDictionary;
                t.FromNameDictionary = FromNameDictionary;
                t.Translate(exp.FindLambda());

                if (CurrentStack.Count > 0)
                    CurrentStack.Push(CurrentStack.Pop().And(t.Result));
                else
                    CurrentStack.Push(t.Result);
            }
        }

        [ExpressionTranslater(MethodName = SELECTMANY)]
        public virtual void SelectManayHandler(MethodCallExpression exp)
        {
        }

        [ExpressionTranslater(MethodName = "Join")]
        public virtual void JoinHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            JoinTranslator t = new JoinTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp);

            //lastJoinSelect = t.SelectExpression;

            GetStack(FROM).Push(GetStack(FROM).Pop().
                           Concat(t.Result, " ").Bracket());
        }

        [ExpressionTranslater(MethodName = GROUPBY)]
        public virtual void GroupByHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            GroupByTranslator t = new GroupByTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp.FindLambda());

            GroupByKeys = t.GroupByKeys;
            GroupByType = t.ObjectType;

            CurrentStack.Push(t.Result);
        }

        [ExpressionTranslater(MethodName = "Distinct")]
        public virtual void DistinctHandler(MethodCallExpression expression)
        {
            if (GetStack(SELECT).Count > 0)
                GetStack(SELECT).Push(DataSource.CreateScriptFactory().
                                 Distinct(GetStack(SELECT).Pop()));
            else
            {
                SelectCurrentResult();
                GetStack(SELECT).Push(DataSource.CreateScriptFactory().
                                 Distinct(GetStack(SELECT).Pop()));
            }
        }

        [ExpressionTranslater(MethodName = "Take")]
        public virtual void TakeHandler(MethodCallExpression exp)
        {
            LambdaTranslator t = new LambdaTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp.Arguments[1]);

            if (GetStack(SELECT).Count == 0)
                SelectCurrentResult();

            GetStack(SELECT).Push(DataSource.CreateScriptFactory().
                             Take(t.Result.
                             Concat(GetStack(SELECT).Pop())));
        }

        [ExpressionTranslater(MethodName = FIRSTORDEFAULT)]
        public virtual void FirstOrDefaultHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count == 0)
                SelectCurrentResult();

            IScriptFactory f = DataSource.CreateScriptFactory();

            GetStack(SELECT).Push(f.
                             Take(f.Constant(1).
                             Concat(GetStack(SELECT).Pop())));

            ResultType = eExpressionResultType.Element;
        }

        //[ExpressionTranslater(MethodName = FIRST)]
        //public virtual void FirstHandler(MethodCallExpression exp)
        //{
        //    if (GetStack(SELECT).Count == 0)
        //        SelectCurrentResult();

        //    IScriptFactory f = DataSource.CreateScriptFactory();

        //    GetStack(SELECT).Push(f.
        //                     Take(f.Constant(1).
        //                     Concat(GetStack(SELECT).Pop())));

        //    ResultType = eExpressionResultType.ElementLeastOne;
        //}

        [ExpressionTranslater(MethodName = ANY)]
        public virtual void AnyHandler(MethodCallExpression exp)
        {
            CurrentStack = GetStack(COUNT);
            CountHandler(exp);
            CurrentStack = GetStack(ANY);

            ResultType = eExpressionResultType.Bool;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        [ExpressionTranslater(MethodName = COUNT)]
        public virtual void CountHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            CurrentStack.Push(DataSource.CreateScriptFactory().Constant(0));

            if (exp.FindLambda() != null)
            {
                CurrentStack = GetStack(WHERE);
                WhereHandler(exp);
                CurrentStack = GetStack(COUNT);
            }

            ResultType = eExpressionResultType.Number;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        [ExpressionTranslater(MethodName = AVG)]
        public virtual void AverageHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            LambdaTranslator t = new LambdaTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp.FindLambda());

            CurrentStack.Push(t.Result);

            ResultType = eExpressionResultType.Number;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        [ExpressionTranslater(MethodName = SUM)]
        public virtual void SumHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            LambdaTranslator t = new LambdaTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp.FindLambda());

            CurrentStack.Push(t.Result);

            ResultType = eExpressionResultType.Number;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        [ExpressionTranslater(MethodName = MAX)]
        public virtual void MaxHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            LambdaTranslator t = new LambdaTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp.FindLambda());

            CurrentStack.Push(t.Result);

            ResultType = eExpressionResultType.Number;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        [ExpressionTranslater(MethodName = MIN)]
        public virtual void MinHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            LambdaTranslator t = new LambdaTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Translate(exp.FindLambda());

            CurrentStack.Push(t.Result);

            ResultType = eExpressionResultType.Number;
            ResultObjectContext = ObjectContextDictionary.Get(exp.Type);
        }

        [ExpressionTranslater(MethodName = "OrderBy")]
        [ExpressionTranslater(MethodName = "ThenBy")]
        public virtual void OrderByHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            OrderByTranslator t = new OrderByTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Order = eOrder.Ascending;
            t.Translate(exp.FindLambda());

            Stack<IScriptFactory> order = GetStack(ORDERBY);

            if (order.Count > 0)
                order.Push(order.Pop().Concat(t.Result, ", "));
            else
                order.Push(t.Result);
        }

        [ExpressionTranslater(MethodName = "OrderByDescending")]
        [ExpressionTranslater(MethodName = "ThenByDescending")]
        public virtual void OrderByDescendingHandler(MethodCallExpression exp)
        {
            if (GetStack(SELECT).Count > 0)
                NewScript();

            OrderByTranslator t = new OrderByTranslator();
            t.DataSource = DataSource;
            t.ObjectContextDictionary = ObjectContextDictionary;
            t.FromNameDictionary = FromNameDictionary;
            t.Order = eOrder.Descending;
            t.Translate(exp.FindLambda());

            Stack<IScriptFactory> order = GetStack(ORDERBY);

            if (order.Count > 0)
                order.Push(order.Pop().Concat(t.Result, ", "));
            else
                order.Push(t.Result);
        }

        [ExpressionTranslater(MethodName = "Paging")]
        public virtual void PagingHandler(MethodCallExpression exp)
        {
        }

        #endregion

        #endregion

        #region Methods - Visit

        protected override Expression VisitConstant(ConstantExpression c)
        {
            if (c.Value is IQueryable)
            {
                Type type = (c.Value as IQueryable).ElementType;
                ResultObjectContext = ObjectContextDictionary.Get(type);
                ElementObjectContext = ResultObjectContext;

                GetStack(FROM).Push(DataSource.CreateScriptFactory().
                    SchemaObject(ElementObjectContext.Schema, ElementObjectContext.TableName).
                    As(DataSource.CreateScriptFactory().
                    Constant(FromNameDictionary.Set(type)).
                    Stress()));
            }

            return c;
        }

        #endregion

        #region Methods - Build

        protected override IScriptFactory OnHandleResult(string name, IScriptFactory f)
        {
            Type type = f.GetType();

            MethodInfo mi = type.GetMethod(name, new Type[] { type });
            if (mi == null)
                return f;

            return mi.Invoke(DataSource.CreateScriptFactory(), new object[] { f }) as IScriptFactory;
        }

        protected override void BeginBuildScript()
        {
            if (ResultType == eExpressionResultType.Number ||
                ResultType == eExpressionResultType.Bool)
                return;

            Stack<IScriptFactory> st = GetStack(SELECT);
            if (GetStack(SELECT).Count == 0)
            {
                //if (lastJoinSelect == null)
                SelectCurrentResult();
                //else
                //    SelectJoin();
            }

            foreach (var item in ResultObjectContext.Columns)
                item.Value.DataColumn.ColumnName = item.Key;
        }

        protected void NewScript()
        {
            GetStack(FROM).Push(
                BuildScript().
                Bracket().
                As(DataSource.CreateScriptFactory().
                Constant(FromNameDictionary.Set(ResultObjectContext.ElementType)).
                Stress()));
        }

        //private void SelectJoin()
        //{
        //    SelectTranslator t = new SelectTranslator();
        //    t.DataSource = DataSource;
        //    t.ObjectContextDictionary = ObjectContextDictionary;
        //    t.FromNameDictionary = FromNameDictionary;
        //    t.LastGroupByKey = lastGroupByKey;
        //    t.Translate(lastJoinSelect);

        //    ResultObjectContext = ObjectContextDictionary.Get(t.ObjectType);

        //    GetStack(SELECT).Push(t.Result);
        //}

        private void SelectCurrentResult()
        {
            SelectObjectContext e = new SelectObjectContext();
            e.Factory = DataSource.CreateScriptFactory();
            e.Install(ResultObjectContext,
                      FromNameDictionary.Get(ResultObjectContext.ElementType));

            GetStack(SELECT).Push(e.BuildSelect(e.SelectObjectItems));

            //lastJoinSelect = null;
        }

        #endregion

        #endregion
    }
}
